package com.meme.editor;


import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.util.Hashtable;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;

import com.meme.editor.event.EntityObjectListener;
import com.meme.editor.event.EntityChangedEvent;
import com.meme.editor.gui.PropertyEditPanel;
import com.meme.editor.level.Actor;
import com.meme.editor.property.value.StringPropertyValue;
import com.meme.editor.property.PropertyFactory;
import com.meme.editor.property.PropertyGroupSet;
import com.meme.editor.property.PropertyValue;
import com.meme.editor.property.Property;
import com.meme.util.FileUtil;
import com.meme.util.MiscUtil;
import com.meme.util.ObjectRef;



public class EntityObject // extends ObjectWithProperties
{
	static Logger logger = Logger.getLogger (EntityObject.class);	
	static {PropertyFactory.init();}
	
	public static boolean sDisableValueValidating = false;


	// public properties
	public final static String GroupName_Entity = "Entity"; 	
	public final static String PropertyName_Name = "Name"; 

	String mName;

	// node path structure
	// now, the children are stored in a vector, which is not efficient when inserting child.
	// If the using of getChildByIndex can be avoided, better to use link table instead. 
	Vector<EntityObject> mChildren = new Vector<EntityObject> ();
	Hashtable<String, EntityObject> mNameChildLookupTable = new Hashtable<String, EntityObject> (); 
	// name is changeable.
	// pending: if use mLookupTable anyway, must notify parent when name changed.
	
	EntityObject mGroupParent;
	int          mIndex; // the index to mGroupParent.mChildren
	
	LinkedList <EntityRefLink> mEntityRefers = new LinkedList <EntityRefLink> (); // self being refed
	LinkedList <EntityRefLink> mRefedEntities = new LinkedList <EntityRefLink> (); // self as a refer
	
	EntityChangedEvent mEntityChangedEvent;
	
//----------------------------------------------------------------------------------------------------
// construct
//----------------------------------------------------------------------------------------------------
	
	/**
	 * Although an entity can be constructed without a name, when it is being inserted into a group,
	 * the group will make an identical name for it automatically. 
	 */
	public EntityObject ()
	{
		this (null, null);
	}
	
	public EntityObject (String name)
	{
		this (name, null);
	}

	public EntityObject (EntityObject parent)
	{
		this (null, parent);
	}

	public EntityObject (String name, EntityObject parent)
	{
		//
		mPropertyGroupSet = new PropertyGroupSet (this);
		mEntityChangedEvent = new EntityChangedEvent (this);
		
		// build property manager
		createPropertyGroup (GroupName_Entity);
		createPublicProperty (GroupName_Entity, PropertyName_Name, PropertyFactory.ValueType_String );
		
		setName (name);
		
		setIndex(-1);
		
		if ( parent != null )
			parent.addChild (this);		
	}
	
	
//-------------------------------------------------------------------
// handle properties
//-------------------------------------------------------------------
		
	
	//@Override
	protected void _handleModifyingPropertyValues (PropertyValueModificationList modications)
	{
		// make some handle to avoid name duplicated
		//...
		
		ListIterator<PropertyValueModification> iter = modications.listIterator();
		while ( iter.hasNext() )
		{
			PropertyValueModification mod = iter.next();
			
			if (   mod.mGroupName.equals (GroupName_Entity)
					&& mod.mPropertyName.equals (PropertyName_Name) )
				{
					EntityObject parent = getParent ();
					if ( parent != null )
					{
						setName ( parent.makeDefaultNameForChild(this, (String)mod.mNewValue) );
					}
				
					iter.remove();
				}
		}
		

			
		//super._handleModifyingPropertyValues (modications);
	}
	
	
	public String getName ()
	{
		return mName;
	}
	
	public void setName (String name)
	{
		//logger.assertLog (name != null, "The name can't be null.");
		
		if ( name == null )
			name = getDefaultName();
		else
		{
			// todo, more checking ...
			// should create a new function to handle this
			
			//logger.assertLog (name.length() > 0, "The name can't be blank.");
			logger.assertLog (! name.startsWith("#"), "The name shouldn't start with '#'.");
			logger.assertLog (! name.startsWith("$"), "The name shouldn't start with '$'.");
			logger.assertLog (! name.startsWith("@"), "The name shouldn't start with '@'.");
			logger.assertLog (! name.startsWith("&"), "The name shouldn't start with '&'.");
			
			while (true)
			{
				if ( name.startsWith("#") )
				{
					name = name.substring(1);
					continue;
				}
				if ( name.startsWith("$") )
				{
					name = name.substring(1);
					continue;
				}
				if ( name.startsWith("@") )
				{
					name = name.substring(1);
					continue;
				}
				if ( name.startsWith("&") )
				{
					name = name.substring(1);
					continue;
				}
				
				break;
			}
		}
		
	
		if ( name.length() == 0 )
			name = getDefaultName();
		
		if ( getParent () != null )
		{
			name = getParent ().makeDefaultNameForChild(this, name);
			
			getParent ().mNameChildLookupTable.remove(mName);
			getParent ().mNameChildLookupTable.put(name, this);
		}

		mName = name;		
		
		changePublicPropertyValue (GroupName_Entity, PropertyName_Name, mName);

		notifyModified (EntityChangedEvent.ModType_NameModified);		
		if (getParent () != null)
			getParent ().notifyModified (EntityChangedEvent.ModType_ChildrenNamesModified);
	}
	
	
	@Override
	public String toString ()
	{
		return getIndex () + " : " + getName ();
	}
	
	protected String  makeDefaultNameForChild (EntityObject child)
	{
		return makeDefaultNameForChild (child, child.getDefaultName());
	}
	
	private int _lastIdInRecommendedName = 1;
	protected String  makeDefaultNameForChild (EntityObject child, String recommendedName)
	{
		// ...
		//logger.assertLog (child != null, "make name for null child?");

		if (recommendedName == null || "".equals(recommendedName) ) 
			recommendedName = child.getDefaultName();
		
		
		if (child.getParent() == this && child.getName().equals(recommendedName))
			return recommendedName;
		
		if ( getChildByName(recommendedName) != null )
		{		
			recommendedName = recommendedName + "_";
			
			while (getChildByName(recommendedName + _lastIdInRecommendedName) != null)
				++ _lastIdInRecommendedName;
			
			recommendedName = recommendedName + _lastIdInRecommendedName;
		}
		
		return recommendedName;
		//child.setName (recommendedName);
	}


//-------------------------------------------------------------------
// Some functions subclasses should override
//-------------------------------------------------------------------
	
	public String getDefaultName ()
	{
		return "Entity";
	}
	
	public boolean isValid ()
	{
		return true;
	}
	
	public boolean isGroupObject ()
	{
		return true;
	}

	/**
	 * Only useful when isGroupObject return true
	 * @param entity
	 * @return
	 */
	protected boolean isValidChild (EntityObject entity)
	{
		return true;
	}
	
	
	public boolean cloneFrom (EntityObject object)
	{
		logger.assertLog (object != null, "object must not be null.");
		logger.assertLog (object.getClass() == getClass (), "Class type must be matched.");
		
		if (object == null) // || object.getClass() != getClass () ) //
			return false;
		
		// the default cloneFrom only clone properties (the name property will not be cloned)
		
		List<Property> propertyList = new Vector <Property> (16);
		
		PropertyGroupSet propertyGroupSet = object.getPublicProperties();
		for ( int groupID = 0; groupID < propertyGroupSet.getPropertyGroupsCont(); ++ groupID )
		{
			for ( int propertyID = 0; propertyID < propertyGroupSet.getPropertiesCountInGroup (groupID); ++ propertyID )
			{
				Property property = propertyGroupSet.getPropertyInGroup (groupID, propertyID);
				propertyList.add (property);
			}
		}
		
		propertyList.remove (object.getPublicProperty (GroupName_Entity, PropertyName_Name));
		
		clonePropertiesFrom (propertyList);
		
		return true;
	}
	
	protected void clonePropertiesFrom (List<Property> propertyList)
	{
		PropertyValueModificationList modications = new PropertyValueModificationList ();
		
		for (Property property : propertyList)
		{
			modications.add (new PropertyValueModification (property.getGroupName(), property.getName(), property.getValue()));
		}
		
		modifyPropertyValues (modications);
	}


	/**
	 * eval some entity object ref properties
	 */
	protected void _evalNodePaths ()
	{
		
	}
	
//---------------------------------------------------------------------------------
// node paths
//---------------------------------------------------------------------------------
	
	
//	/ : getChildByName
//	/#: getChildByIndex
//	/$: getProperty
//
//	when parsing node path, sometimes
//	- an int value is excepted
//	- an entity is excepted
//	- an entity list is excepted
//
//	node path
//	- $__self/$model/$animations  ($model is excepted as an entity object)
//
//	- $__parent
//	- $__self
//	- $__root 
//	- $__host
//	- /$__children[child_name]  <=> /child_name
//	  /$__children[#N]

	
	public final void evalNodePaths ()
	{
		_evalNodePaths ();
		
		for (EntityObject entity : mChildren)
		{
			entity.evalNodePaths();
		}
	}	

	// to do
	public String getAbsoluteNodePath ()
	{
		return null;
	}
	
	// to do
	public String getHostedNodePath ()
	{
		return null;
	}
	
	public String getNodePathRelativeTo (EntityObject relative)
	{
		return null;
	}
	
	
	
	/**
	 * 
	 * @param nodePath
	 * @return
	 */
	public Object parseNodePath (String nodePath)
	{
		logger.assertLog (nodePath != null, "node path can't be null.");
		
		if (nodePath == null)
			return null;
		
		//logger.assertLog (nodePath.length() > 0, "the length of node path must be lager than zero.");
		
		if ( nodePath.length() == 0 )
			return null;
		
		if ( nodePath.equals ("/") )
			return getRoot ();
		
		if ( nodePath.charAt(nodePath.length() - 1) == '/' )
			return null;
		
		Object owner = this;
		
		do {
			int slashPos = nodePath.indexOf('/');
			
			String propertyName;
			String newNodePath;
			
			if (slashPos < 0)
			{
				propertyName = nodePath;
				newNodePath = null;
			}
			else
			{
				propertyName = nodePath.substring (0, slashPos);
				newNodePath = nodePath.substring(slashPos + 1);
			}
			
			Object who = null;
			
			if (owner == this)
			{
				if ( isShortCutPropertyName (propertyName) )
				{
					who = getProperty (propertyName);
				}
				else if ( propertyName.startsWith ("$") )
				{
					who = getProperty ( propertyName.substring (1) );
				}
				else
				{
					who = getChild (propertyName);
				}
			}
			else
			{
				if (owner.getClass().isArray())
				{
					Object[] objects = (Object[]) owner;
					for (Object object : objects)
					{
						if (object.toString().equals(propertyName))
						{
							who = object;
							break;
						}
					}
				}
				else if (owner instanceof List)
				{
					List objects = (List) owner;
					for (Object object : objects)
					{
						if (object.toString().equals(propertyName))
						{
							who = object;
							break;
						}
					}					
				}
				else if (owner instanceof Hashtable)
				{
					Hashtable table = (Hashtable) owner;
					who = table.get(propertyName);
				}
			}
			
			if (who == null)
				return null;
			
			if ( newNodePath == null )
				return who;
			
			if (who instanceof EntityObject)
			{
				
				// who is expected as an EntityObject
				EntityObject entity = (EntityObject)who;
				return entity.parseNodePath (newNodePath);
			}
			
			owner = who;
			nodePath = newNodePath;
			
		} while (true);

	}
	
	public EntityObject getChild (String childName)
	{
		logger.assertLog (childName != null, "Invalid child name.");
		
		if ( childName.startsWith("#") )
		{
			return getChildByIndex ( Integer.parseInt (childName.substring(1)) );
		}
		else
		{
			return getChildByName (childName);
		}
	}
	
	public boolean isShortCutPropertyName (String shortcut)
	{
		if ( shortcut == null )
			return false;
		
		if ( shortcut.equals ("") 
			|| shortcut.equals ("..")
			|| shortcut.equals (".") 
			|| shortcut.equals ("@") 
		) return true;
		
		return false;
	}
	
	public Object getProperty (String propertyName)
	{
		if ( propertyName.equals ("_root") || propertyName.equals ("") )
			return getRoot ();
		if ( propertyName.equals ("_parent") || propertyName.equals ("..") )
			return getParent ();
		if ( propertyName.equals ("_self") || propertyName.equals (".") )
			return this;
		if ( propertyName.equals ("_host") || propertyName.equals ("@")  )
			return getHost ();
		if ( propertyName.startsWith ("_children") )
		{
			// get child if [] is specified
			// ...
			
			return mChildren;
		}
		
		return null;
	}
	
//--------------------------------------------------------------------------------------------------
// 
//--------------------------------------------------------------------------------------------------
	

	
	String mHostFilename = null;
	boolean mModified;
	
	public boolean isHostObject ()
	{
		return mHostFilename != null;
	}
	
	public void setHostFilename (String filename)
	{
		mHostFilename = FileUtil.getCanonicalPathFileName(filename);
		
		if ( mHostFilename == null && filename != null )
			mHostFilename = ""; // to make isHostObject return true
	}
	
	public String getHostFilename ()
	{
		return mHostFilename;
	}
	
	public boolean isModified ()
	{
		return mModified;
	}
	
	public void setModified (boolean modified)
	{
		mModified = modified;
	}
	

//	public void loadXml (Document domDocument)
//	{
//
//	}
//	
//
//	public void writeXml (Document domDocument)
//	{
//		
//	}
	
	// for non-host nodes
//		public void loadXml (XmlNode node)
//		{
//
//		}
//	
//
//		public void writeXml (XmlNode node)
//		{
//			
//		}
	




	public void render (Object context)
	{

	}
	
	
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------

	

	
	
	
	protected void breakEntityRefLinks ()
	{
		LinkedList <EntityRefLink> tempList = new LinkedList <EntityRefLink> ();
		
		tempList.addAll (mRefedEntities);
		tempList.addAll (mEntityRefers);	
		for (EntityRefLink link : tempList)
			link.setRefedEntity (null);
		
		logger.assertLog(mEntityRefers.size() == 0, "Now the refer list should be empty.");
	}
	
	public boolean checkReferability (EntityRefLink link, EntityObject refed)
	{
		return true;
	}
	
	public void notifyEntityRefLinkModified (EntityRefLink link)
	{
		
	}

	public void notifySelfModified ()
	{
		fireEntityChangedEvents (false, true);
	}
	
	// fire events
	public void commitModifications ()
	{
		notifyModified (EntityChangedEvent.ModType_General, true);
	}
	
	// accumulate events
	public void notifyModified (int modType)
	{
		notifyModified (modType, false);
	}
	
	// when bFireEvent is false, just accumulate events,
	// when bFireEvent is true,  send all accumulated events to listeners (for all entities)
	protected void notifyModified (int modType, boolean bFireEvent)
	{
		mModified = true;
		
		// register
		if ( mEntityChangedEvent.registerModification (modType) )
		{
			// notify parent
			if ( getParent() != null )
				getParent().notifyModified (EntityChangedEvent.ModType_General);
			
			// notify children (temp not)
			
			// notify refed entities (temp not)
			
			// notify refers
			for (EntityRefLink link : mEntityRefers)
				link.getEntityRefer().notifyModified (EntityChangedEvent.ModType_General);
		}
		
		// fire ...
		if ( bFireEvent )
			getRoot ().fireEntityChangedEvents (true);
	}
	


	// can be overrided
//	public void responseEventFromParent (int parentModType)
//	{
//		// notifyModified (EntityChangedEvent.ModType_General);
//	}
	
	// can be overrided
//	public void responseEventFromEntityRefer (int referModType, EntityRefLink link)
//	{
//		//notifyModified (EntityChangedEvent.ModType_General);
//	}
//	
//	public void responseEventFromRefedEntity (int refedModType, EntityRefLink link)
//	{
//		notifyModified (EntityChangedEvent.ModType_General);
//	}
	

	
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------


	
	public EntityObject getParent ()
	{
		return mGroupParent;
	}
	
	public EntityObject getRoot ()
	{
		EntityObject entity = this;
		EntityObject parent = entity.getParent();
		while (parent != null)
		{
			entity = parent;
			parent = entity.getParent();
		}
		
		return entity;
	}
	
	public EntityObject getHost ()
	{
		EntityObject entity = this;
		while ( entity != null && ! entity.isHostObject() )
		{
			entity = entity.getParent();
		}
		
		logger.assertLog (entity != null, "Every enrity should has a host ancestor.");
		
		return entity;
	}
	
	//public
	private void setParent (EntityObject parent)
	{
		mGroupParent = parent;
	}
	


	
	public boolean isParentOf (EntityObject entity)
	{
		if ( entity == null )
			return false;
		
		return this == entity.getParent();
	}
	
	public boolean isChildOf (EntityObject entity)
	{
		if ( entity == null )
			return false;
		
		return getParent () == entity;
	}
	
	public boolean isAncestorOf (EntityObject entity)
	{
		if ( entity == null )
			return false;
		
		EntityObject parent = entity.getParent();
		while (parent != null)
		{
			if (parent == this)
				return true;
			
			parent = parent.getParent();
		}
		
		return false;
	}
	
	public boolean isDescendantOf (EntityObject entity)
	{
		if ( entity == null )
			return false;
		
		return entity.isAncestorOf(this);
	}
	
	public int getIndex ()
	{
		return mIndex;
	}
	
	protected void setIndex (int index)
	{
		mIndex = index;
		
		notifyModified (EntityChangedEvent.ModType_IndexModified);
		if (getParent () != null)
			getParent ().notifyModified (EntityChangedEvent.ModType_ChildrenIndexesModified);
	}

	
	
	public Vector<EntityObject> getChildren ()
	{
		return mChildren;
	}
	
	public int getChildrenCount ()
	{
		if ( mChildren == null ) 
			return 0;
		
		return mChildren.size ();
	}
	
	public EntityObject getChildByIndex (int index)
	{
		if ( mChildren == null )
		{
			logger.warn ("mChildren is null !!!");
			
			return null;
		}
		
		logger.assertLog (mChildren.size () > index && index >= 0, "getChildByIndex: index out of range");
		
		return mChildren.elementAt (index);
	}
	
	
	public EntityObject getChildByName (String name)
	{
		if ( mChildren == null )
		{
			logger.warn ("mChildren is null !!!");
			
			return null;
		}
		
		if (name.startsWith("#"))
		{
			int id = Integer.parseInt (name.substring(1));
			
			return getChildByIndex(id);
		}
		
		// todo: use lookup table to optimize
//		for ( int i = mChildren.size () - 1; i >= 0; -- i )
//		{
//			EntityObject entity = mChildren.elementAt (i);
//			if ( entity.getName().equals (name) )
//				return entity;
//		}
//		
//		
//		return null;

		return mNameChildLookupTable.get(name);
}
	
	public EntityObject insertChild (EntityObject child, int pos)
	{
		if ( ! isGroupObject () )
		{
			logger.warn ("This is not a group object.");
			
			return null;
		}

		if ( child == null )
		{
			logger.assertLog (false, "Can't insert null child.");
			return null;
		}
		
		if ( ! isValidChild (child) )
		{
			logger.assertLog (false, "This is an invalid child candidate.");
			return null;
		}
		
		if ( child == this )
		{
			logger.assertLog (false, "Can't insert self as a child.");
			return null;
		}
		
		// must be not noull now
//			if ( mChildren == null )
//				mChildren = new Vector<EntityObject> ();
		
		
		EntityObject parent = child.getParent();
		if ( parent != null )
		{
			if ( parent == this )
			{
				logger.assertLog (mChildren.size () - 1 >= pos && pos >= 0, "insertChild, 1: index out of range");
				if ( pos < 0 ) pos = 0;
				if ( pos > mChildren.size () - 1 ) pos = mChildren.size () - 1;
				
				if ( child.getIndex() == pos )
					return child; // no need to go on
				
				// here, the code is not efficient !!!
				int dir =  pos > child.getIndex() ? 1 : -1;
				int id =  child.getIndex();
				while (id != pos)
				{
					EntityObject neighbour = getChildByIndex (id + dir);
					mChildren.set (id, neighbour);
					neighbour.setIndex (id);
					
					id += dir;
				}
				
				mChildren.set ( pos, child );
				child.setIndex (pos);
				
				return child;
			}

			parent.removeChild (child);
		}
		
		// 
		EntityObject ancestor = getParent ();
		while (ancestor != null)
		{
			if ( ancestor == child )
			{
				logger.assertLog (false, "Do you really add an ancestor as a child");
				return null;
			}
			
			ancestor = ancestor.getParent ();
		}
		
		// must have a name and the name must be identical
		if (child.getName() == null )
		{
			child.setName ( makeDefaultNameForChild (child) );
			
			//logger.info ("Make a default name for child.");
		}
		else if (getChildByName (child.getName()) != null)
		{
			child.setName ( makeDefaultNameForChild (child, child.getName()) );
			
			//logger.info ("Rename child.");
		}
		
		//
		logger.assertLog (mChildren.size () >= pos && pos >= 0, "insertChild, 2: index out of range");
		if ( pos < 0 ) pos = 0;
		if ( pos > mChildren.size () ) pos = mChildren.size ();			
		
		//
		mChildren.add(pos, null);
		
		for (int i = pos + 1; i < mChildren.size(); ++ i)
		{
			getChildByIndex (i).setIndex (i);
		}
		
		//
		replaceChild (pos, child);

		
		return child;
	}
	
	public EntityObject addChild (EntityObject child)
	{		
		if ( child == null || child.getParent() == this )
			return child;
			
		return insertChild ( child, getChildrenCount () );
	}
	
	public EntityObject addNewChild ()
	{
		return insertNewChild ( getChildrenCount () );
	}
	
	public EntityObject insertNewChild (int insertPos)
	{
		// do nothing here
		// some sub classes will override this function
		
		logger.warn ("Should call overrided one in sub class.");
		
		return null;
	}
	
	public void removeChild (EntityObject entity)
	{
		logger.assertLog (entity != null, "Can't remvoe null entity");
		logger.assertLog (entity.getParent() == this, "This entity is not my child.");
		logger.assertLog (getChildByIndex ( entity.getIndex() ) == entity, "The indexes are not matched.");
				
		if (entity.getParent() == this)
			removeChild (entity.getIndex());
	}

	public void removeChild (int index)
	{
		replaceChild (index, null);
		
		mChildren.remove (index);

		for (int i = index; i < mChildren.size(); ++ i)
		{
			getChildByIndex (i).setIndex (i);
		}
	}
	
	public void remove ()
	{
		if ( getParent () != null )
			getParent ().removeChild (this);
	}

	public void removeAllChildren ()
	{
		while (getChildrenCount () > 0)
			removeChild (0);
	}
	
	public void swapChildren (int one, int another)
	{
//		logger.assertLog (mChildren.size () > one && one >= 0, "index (one) out of range");
//		logger.assertLog (mChildren.size () > another && another >= 0, "index (another) out of range");
		
		EntityObject entityone     = mChildren.elementAt (one);
		EntityObject entityanother = mChildren.elementAt (another);
		mChildren.set ( one, entityanother );
		entityanother.setIndex ( one );
		mChildren.set ( another, entityone );
		entityone.setIndex ( another );
	}
	

	protected void nofityAncestorRemoved (EntityObject removedAncestor)
	{
		if ( removedAncestor == this || isDescendantOf (removedAncestor) )
		{
			if ( removedAncestor != this )
				breakEntityRefLinks();
			
			for (EntityObject child : getChildren ())
			{
				child.nofityAncestorRemoved (removedAncestor);
			}
		}
	}
	
	public void replaceChild (int pos, EntityObject newChild)
	{
		EntityObject oldChild = getChildByIndex (pos);
		
		// get rid of old
		if ( oldChild != null )
		{
			if (newChild == oldChild)
			{
				oldChild.setIndex (pos);
				return;
			}
			
			oldChild.breakEntityRefLinks();
			oldChild.setParent(null);
			oldChild.setIndex(-1);
			
			oldChild.nofityAncestorRemoved(oldChild);

			mNameChildLookupTable.remove(oldChild.getName());
			
			mChildren.set(pos, null);
			
			oldChild.notifyModified (EntityChangedEvent.ModType_Removed);
			notifyModified (EntityChangedEvent.ModType_ChildrenRemoved);
		}
		
		// put new
		if ( newChild != null )
		{
			newChild.setParent (this);
			newChild.setIndex (pos);
			
			mNameChildLookupTable.put(newChild.getName(), newChild);
			
			mChildren.set(pos, newChild);
			
			notifyModified (EntityChangedEvent.ModType_ChildrenAppended);
			newChild.notifyModified (EntityChangedEvent.ModType_Appended);
		}
	}
	

	
	
//--------------------------------------------------------------------------------------------------
// a simple listener mechanism
//--------------------------------------------------------------------------------------------------
	
	HashSet<EntityObjectListener> mEntityObjectListeners;

	
	public void addEntityObjectListener (EntityObjectListener listener)
	{
		if ( mEntityObjectListeners == null )
			mEntityObjectListeners = new HashSet<EntityObjectListener>();
		
		mEntityObjectListeners.add (listener);
	}
	
	public void removeEntityObjectListener (EntityObjectListener listener)
	{
		mEntityObjectListeners.remove (listener);
	}	
	
	// temp
	
	public void fireEntityChangedEvents (boolean bRecursive)
	{
		fireEntityChangedEvents (bRecursive, false);
	}
	
	public void fireEntityChangedEvents (boolean bRecursive, boolean fireVoidEvent)
	{
		if ( _bDisableFiringPropertyModifiedEvent )
			return; // wait a moment
		
		// children
		if (bRecursive)
		{
			for (EntityObject child : getChildren())
				child.fireEntityChangedEvents (bRecursive, fireVoidEvent);
		}

		// self
		if ( mEntityChangedEvent.hasEventsToFire () || fireVoidEvent )
		{		
			if ( mEntityObjectListeners != null )
			{			
				Iterator<EntityObjectListener> iter = mEntityObjectListeners.iterator();
				while ( iter.hasNext() )
				{
					EntityObjectListener listener = iter.next();
					listener.notifyEntityChanged (mEntityChangedEvent);
				}
			}
			
			mEntityChangedEvent.reset ();
		}
	}
		
//--------------------------------------------------------------------------------------------------
// modify properties 
//--------------------------------------------------------------------------------------------------
	
	
	boolean _bDisableFiringPropertyModifiedEvent = false;
	
	// modify properties and notify listeners.
	// some member variables will be synchronized with these properties. 
	// generally, called by PropertyEditPanel or PropertyValue/PropertyControl
	public final void modifyPropertyValues (PropertyValueModificationList modications)
	{
		// In the process, some functions will call firePropertyModifiedEvent (temp, shouldn't),
		// that is unnecessarily, for firePropertyModifiedEvent will be called at the 
		// end of this function anyway.
		_bDisableFiringPropertyModifiedEvent = true;

		try
		{
			logger.assertLog (modications != null, "modications can't be null");
			
			// try to make the info completed
			for (PropertyValueModification mod : modications)
			{
				if ( mod.mGroupName == null )
				{
					Property property = getPublicProperties().getProperty (mod.mPropertyName);
					if ( property != null ) // this may be a fake property (not a public property)
						mod.mGroupName    = getPublicProperties().getPropertyGroupName (property);
				}
			}
			
			// in this method, some PropertyValueModification might already be remove from modications
			_handleModifyingPropertyValues (modications);
			
			// modify the remainings,
			for (PropertyValueModification mod : modications)
				changePublicPropertyValue (mod.mGroupName, mod.mPropertyName, mod.mNewValue);
		}
		catch (Exception e)
		{
			logger.error (e);
		}
		
		
		_bDisableFiringPropertyModifiedEvent = false;
		
		//fireEntityChangedEvents (true);
		notifyModified(EntityChangedEvent.ModType_PublicPropertyValuesModified, true);
	}

	public final void modifyPropertyValueInGroup (String groupName, String propertyName, Object propertyValue)
	{
		PropertyValueModificationList mods = new PropertyValueModificationList();
		mods.add (new PropertyValueModification (groupName, propertyName, propertyValue));
		                      
		modifyPropertyValues (mods);
	}	

	// not recommended,
	// for debug
	// or for fake property
	public final void modifyPropertyValue (String propertyName, Object propertyValue)
	{
		modifyPropertyValueInGroup (null, propertyName, propertyValue);
	}	

	// modify public property without notifying listeners, also without modifying member variables.
	// this method should be used to synchronize the corresponding Property object when a member is modified.
	public void changePublicPropertyValue (String groupName, String propertyName, Object newValueCandidate)
	{
		Property property = getPublicProperty (groupName, propertyName);
		
		if ( property != null )
		{
			property.modifyValue(newValueCandidate);
			
			notifyModified (EntityChangedEvent.ModType_PublicPropertyValuesModified);
		}
		else
			logger.error("The property specified with name [" + propertyName + "] in group [" + groupName + "] doesn't exist.");
	}
	
	
//--------------------------------------------------------------------------------------------------
// util methods to create public properties
//--------------------------------------------------------------------------------------------------
			
	
	// this variable is to present some public properties for PropertyEditPanel
	PropertyGroupSet mPropertyGroupSet;
	
	public PropertyGroupSet getPublicProperties ()
	{
		return mPropertyGroupSet;
	}
	
	public Property getPublicProperty (String groupName, String propertyName)
	{
		return getPublicProperties ().getPropertyInGroup (groupName, propertyName);
	}

	// the panel used to edit some properties.
	// now most one PropertyEditPanel can be specified
	PropertyEditPanel mPropertyEditPanel;
	
	
	// temp
	public void setPropertyEditPanel (PropertyEditPanel propertyPanel)
	{		
		if ( mPropertyEditPanel != null )
			removeEntityObjectListener (mPropertyEditPanel);
		
		mPropertyEditPanel = propertyPanel;
		
		if ( mPropertyEditPanel != null )
			addEntityObjectListener (mPropertyEditPanel);
		else
			disposePropertyControlComponents ();
			
	}
	
	
	public void createPropertyGroup (String groupName)
	{
		getPublicProperties().createPropertyGroup (groupName);
	}
	
	public Property createPublicProperty (String groupName, 
			                           String propertyName, 
			                           String valueType, 
			                           String controlType, 
			                           boolean editable)
	{
		Property property = getPublicProperties().createProperty(groupName, propertyName, valueType);
		property.setControlType (controlType);
		property.setEditable(editable);
		
		return property;
	}
	

	
	public Property createPublicProperty (String groupName, String propertyName, String valueType)
	{
		return createPublicProperty (groupName, propertyName, valueType, null, true);
	}

	public void disposePropertyControlComponents ()
	{
		PropertyGroupSet propertyGroupSet = getPublicProperties ();
		for ( int groupID = 0; groupID < propertyGroupSet.getPropertyGroupsCont(); ++ groupID )
		{
			for ( int propertyID = 0; propertyID < propertyGroupSet.getPropertiesCountInGroup (groupID); ++ propertyID )
			{
				Property property = propertyGroupSet.getPropertyInGroup (groupID, propertyID);
				property.disposeControlComponent ();
			}
		}
	}

//--------------------------------------------------------------------------------------------------
// for debug
//--------------------------------------------------------------------------------------------------
		
	Object mUserObject;
	
	public void setUserObject (Object object)
	{
		mUserObject = object;
	}
	
	public Object getUserObject ()
	{
		return mUserObject;
	}
	
//--------------------------------------------------------------------------------------------------
// for debug
//--------------------------------------------------------------------------------------------------
		
	// 
	public void dumpChildren (boolean recursive, int depth)
	{
		if ( depth == 0)
			logger.debug ("----------------- Children Dump ------------------------------");
		
		logger.debug ( MiscUtil.makeStringWithSomeSpaces(depth) + toString () );

		if ( mChildren != null )
		{
			for ( int i = 0; i < mChildren.size(); ++ i )
			{
				EntityObject child = mChildren.elementAt (i);
				
				if ( recursive || depth == 0 )
					child.dumpChildren(recursive, depth + 3);
			}
		}
		
		if ( depth == 0 )
			logger.debug ("----------------- Dump End -----------------------------------");
	}

//--------------------------------------------------------------------------------------------------
// 
//--------------------------------------------------------------------------------------------------
	


}
